package comtest;

import gnu.io.CommPortIdentifier;
import gnu.io.NoSuchPortException;
import gnu.io.PortInUseException;
import gnu.io.SerialPort;
import gnu.io.UnsupportedCommOperationException;
import java.io.BufferedWriter;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.util.HashMap;
import java.util.Map;

public class Main {

    private static Map<Integer, Track> TrackList = new HashMap<Integer, Track>();

    public static void main(String[] args) throws NoSuchPortException, PortInUseException, IOException, UnsupportedCommOperationException, InterruptedException {
        CommPortIdentifier portId = CommPortIdentifier.getPortIdentifier("/dev/ttyUSB0");
        //CommPortIdentifier portId = CommPortIdentifier.getPortIdentifier("COM3");
        SerialPort serialPort = (SerialPort) portId.open("SimpleWrite", 2000);

        OutputStream outputStream = serialPort.getOutputStream();
        InputStream inputStream = serialPort.getInputStream();

        serialPort.setSerialPortParams(57600,
                SerialPort.DATABITS_8,
                SerialPort.STOPBITS_1,
                SerialPort.PARITY_NONE);

        byte[] command_b = new byte[5];

        // Getting info
        command_b[0] = (byte) 0x02;
        command_b[1] = (byte) 0x00;
        command_b[2] = (byte) 0x01;
        command_b[3] = (byte) 0x85;
        command_b[4] = (byte) 0x84;
        outputStream.write(command_b);
        Thread.sleep(1500);

        byte[] info_b = new byte[255];
        inputStream.read(info_b);
        parseInfo(info_b);

        // Getting track list
        command_b[0] = (byte) 0x02;
        command_b[1] = (byte) 0x00;
        command_b[2] = (byte) 0x01;
        command_b[3] = (byte) 0x78;
        command_b[4] = (byte) 0x79;
        outputStream.write(command_b);
        Thread.sleep(1500);

        byte[] trlist_b = new byte[255];
        inputStream.read(trlist_b);
        parseTrackList(trlist_b);

        System.out.println(TrackList.size() + " tracks found.");
        for (Track tr : TrackList.values()) {
            System.out.println("Track " + tr.ID + " - " + tr.Distance + " m, " + tr.Laps + " laps, " + tr.Points + " points.");
        }

        // Getting track
        //Integer TrackID = TrackList.get(0).ID;
        Integer TrackID = 0;

        byte[] command_b_t = new byte[9];
        command_b_t[0] = (byte) 0x02;
        command_b_t[1] = (byte) 0x00;
        command_b_t[2] = (byte) 0x05;
        command_b_t[3] = (byte) 0x80;
        command_b_t[4] = (byte) 0x00;
        command_b_t[5] = (byte) 0x01;
        command_b_t[6] = (byte) (TrackID / 256);
        command_b_t[7] = (byte) (TrackID - command_b_t[6]);

        byte checksum = (byte) 0x00;
        checksum ^= command_b_t[2];
        checksum ^= command_b_t[3];
        checksum ^= command_b_t[4];
        checksum ^= command_b_t[5];
        checksum ^= command_b_t[6];
        checksum ^= command_b_t[7];
        command_b_t[8] = checksum;

        outputStream.write(command_b_t);
        Thread.sleep(1500);

        byte[] trackseg = new byte[4096]; // 2070
        inputStream.read(trackseg);

        int SegCounter = 0;
        while (SegCounter < 500) {
            parseTrackSegment(TrackID, trackseg);
            SegCounter++;
            command_b[0] = (byte) 0x02;
            command_b[1] = (byte) 0x00;
            command_b[2] = (byte) 0x01;
            command_b[3] = (byte) 0x81;
            command_b[4] = (byte) 0x80;
            outputStream.write(command_b);
            Thread.sleep(1500);
            inputStream.read(trackseg);
            if (trackseg[0] == (byte) 0x8A) {
                SegCounter = 501;
            }
        }

        serialPort.close();

        //Saving track in my own format
        SaveTrack(TrackID);

    }

    private static void SaveTrack(Integer TrackID) {
        BufferedWriter bw = null;
        try {
            bw = new BufferedWriter(
                    new OutputStreamWriter(
                    new FileOutputStream("test.track"), "UTF-8"));
        } catch (IOException ex) {
            System.out.println(ex.toString());
        }

        try {
            bw.write("Track - " +
                    TrackList.get(TrackID).Year.toString() + "-" +
                    TrackList.get(TrackID).Month.toString() + "-" +
                    TrackList.get(TrackID).Day.toString() + " " +
                    TrackList.get(TrackID).Hour.toString() + ":" +
                    TrackList.get(TrackID).Min.toString() + ":" +
                    TrackList.get(TrackID).Sec.toString());
            bw.newLine();
            bw.write("===============================");
            bw.newLine();

            bw.write("track:");
            bw.newLine();
            bw.write(TrackList.get(TrackID).Year.toString());
            bw.newLine();
            bw.write(TrackList.get(TrackID).Month.toString());
            bw.newLine();
            bw.write(TrackList.get(TrackID).Day.toString());
            bw.newLine();
            bw.write(TrackList.get(TrackID).Hour.toString());
            bw.newLine();
            bw.write(TrackList.get(TrackID).Min.toString());
            bw.newLine();
            bw.write(TrackList.get(TrackID).Sec.toString());
            bw.newLine();
            bw.write(TrackList.get(TrackID).Laps.toString());
            bw.newLine();
            bw.write(TrackList.get(TrackID).Duration_msec.toString());
            bw.newLine();
            bw.write(TrackList.get(TrackID).Duration_h.toString());
            bw.newLine();
            bw.write(TrackList.get(TrackID).Duration_m.toString());
            bw.newLine();
            bw.write(TrackList.get(TrackID).Duration_s.toString());
            bw.newLine();
            bw.write(TrackList.get(TrackID).Distance.toString());
            bw.newLine();
            bw.write(TrackList.get(TrackID).Calories.toString());
            bw.newLine();
            bw.write(TrackList.get(TrackID).TopSpeed.toString());
            bw.newLine();
            bw.write(TrackList.get(TrackID).TopHeartBeat.toString());
            bw.newLine();
            bw.write(TrackList.get(TrackID).AvgHeartBeat.toString());
            bw.newLine();
            bw.write(TrackList.get(TrackID).AscHeight.toString());
            bw.newLine();
            bw.write(TrackList.get(TrackID).DescHeight.toString());
            bw.newLine();
            bw.write(TrackList.get(TrackID).Points.toString());
            bw.newLine();
            bw.write("===============================");
            bw.newLine();

            for (TrackLap tl : TrackList.get(TrackID).tls) {
                bw.write("lap:");
                bw.newLine();
                bw.write(tl.Stop.toString());
                bw.newLine();
                bw.write(tl.Duration.toString());
                bw.newLine();
                bw.write(tl.Distance.toString());
                bw.newLine();
                bw.write(tl.Calories.toString());
                bw.newLine();
                bw.write(tl.MaxSpeed.toString());
                bw.newLine();
                bw.write(tl.MaxHR.toString());
                bw.newLine();
                bw.write(tl.AvgHR.toString());
                bw.newLine();
                bw.write(tl.Points.toString());
                bw.newLine();
                bw.write("===============================");
                bw.newLine();
            }

            for (TrackPoint tp : TrackList.get(TrackID).tps) {
                bw.write("point:");
                bw.newLine();
                bw.write(tp.Lat.toString());
                bw.newLine();
                bw.write(tp.Lon.toString());
                bw.newLine();
                bw.write(tp.Alt.toString());
                bw.newLine();
                bw.write(tp.Speed.toString());
                bw.newLine();
                bw.write(tp.HeartRate.toString());
                bw.newLine();
                bw.write(tp.TimeShift.toString());
                bw.newLine();
                bw.write("===============================");
                bw.newLine();
            }

            bw.close();
        } catch (IOException ex) {
            System.out.println(ex.toString());
        }
    }

    private static void LoadTrack(Integer TrackID) {
    }

    private static void parseTrackSegment(Integer TrackID, byte[] tseg) {
        int sp = 30;

        Integer trg = uByteToInt(tseg[sp]) * 256 + uByteToInt(tseg[sp + 1]);

        if (trg.equals(0xffff)) {
            sp = 34;
            while (sp < 2057) {
                TrackLap tl = new TrackLap();

                tl.Stop =
                        uByteToInt(tseg[sp + 0]) * 16777216 +
                        uByteToInt(tseg[sp + 1]) * 65536 +
                        uByteToInt(tseg[sp + 2]) * 256 +
                        uByteToInt(tseg[sp + 3]);
                sp++;
                sp++;
                sp++;
                sp++;

                tl.Duration =
                        uByteToInt(tseg[sp + 0]) * 16777216 +
                        uByteToInt(tseg[sp + 1]) * 65536 +
                        uByteToInt(tseg[sp + 2]) * 256 +
                        uByteToInt(tseg[sp + 3]);
                sp++;
                sp++;
                sp++;
                sp++;

                tl.Distance =
                        uByteToInt(tseg[sp + 0]) * 16777216 +
                        uByteToInt(tseg[sp + 1]) * 65536 +
                        uByteToInt(tseg[sp + 2]) * 256 +
                        uByteToInt(tseg[sp + 3]);
                sp++;
                sp++;
                sp++;
                sp++;

                tl.Calories = uByteToInt(tseg[sp]) * 256 + uByteToInt(tseg[sp + 1]);
                sp++;
                sp++;

                tl.MaxSpeed = uByteToInt(tseg[sp]) * 256 + uByteToInt(tseg[sp + 1]);
                sp++;
                sp++;

                tl.MaxHR = uByteToInt(tseg[sp]);
                sp++;
                tl.AvgHR = uByteToInt(tseg[sp]);
                sp++;

                tl.Points =
                        uByteToInt(tseg[sp + 0]) * 16777216 +
                        uByteToInt(tseg[sp + 1]) * 65536 +
                        uByteToInt(tseg[sp + 2]) * 256 +
                        uByteToInt(tseg[sp + 3]);
                sp++;
                sp++;
                sp++;
                sp++;

                TrackList.get(TrackID).tls.add(tl);
                System.out.println("Lap " + TrackList.get(TrackID).tls.size() + " of " + TrackList.get(TrackID).Laps);

                if (TrackList.get(TrackID).Laps.equals(TrackList.get(TrackID).tls.size())) {
                    sp = 2070;
                }
            }
        } else {
            sp = 34;
            while (sp < 2074) {
                TrackPoint tp = new TrackPoint();
                tp.Lat =
                        uByteToInt(tseg[sp + 0]) * 16777216 +
                        uByteToInt(tseg[sp + 1]) * 65536 +
                        uByteToInt(tseg[sp + 2]) * 256 +
                        uByteToInt(tseg[sp + 3]);
                sp++;
                sp++;
                sp++;
                sp++;

                tp.Lon =
                        uByteToInt(tseg[sp + 0]) * 16777216 +
                        uByteToInt(tseg[sp + 1]) * 65536 +
                        uByteToInt(tseg[sp + 2]) * 256 +
                        uByteToInt(tseg[sp + 3]);
                sp++;
                sp++;
                sp++;
                sp++;

                tp.Alt = uByteToInt(tseg[sp]) * 256 + uByteToInt(tseg[sp + 1]);
                sp++;
                sp++;

                tp.Speed = uByteToInt(tseg[sp]) * 256 + uByteToInt(tseg[sp + 1]);
                sp++;
                sp++;

                tp.HeartRate = uByteToInt(tseg[sp]);
                sp++;

                tp.TimeShift = uByteToInt(tseg[sp]) * 256 + uByteToInt(tseg[sp + 1]);
                sp++;
                sp++;

                TrackList.get(TrackID).tps.add(tp);
                System.out.println("Point " + TrackList.get(TrackID).tps.size() + " of " + TrackList.get(TrackID).Points);

                if (TrackList.get(TrackID).Points.equals(TrackList.get(TrackID).tps.size())) {
                    sp = 2075;
                }
            }
        }
    }

    private static void parseTrackList(byte[] info) {
        TrackList.clear();

        int trcount = 0;
        int c;
        while (trcount < DeviceInfo.TracksCount) {
            Track ti = new Track();

            c = 3 + trcount * 31;

            ti.Year = uByteToInt(info[c]) + 2000;
            c++;
            ti.Month = uByteToInt(info[c]);
            c++;
            ti.Day = uByteToInt(info[c]);
            c++;
            ti.Hour = uByteToInt(info[c]);
            c++;
            ti.Min = uByteToInt(info[c]);
            c++;
            ti.Sec = uByteToInt(info[c]);
            c++;

            ti.Laps = uByteToInt(info[c]);
            c++;

            ti.Duration_msec = uByteToInt(info[c]) * 16777216 +
                    uByteToInt(info[c + 1]) * 65536 +
                    uByteToInt(info[c + 2]) * 256 +
                    uByteToInt(info[c + 3]);
            c++;
            c++;
            c++;
            c++;
            ti.Duration_h = (ti.Duration_msec / 36000);
            ti.Duration_m = (ti.Duration_msec - ti.Duration_h * 36000) / 600;
            ti.Duration_s = (ti.Duration_msec - ti.Duration_h * 36000 - ti.Duration_m * 600) / 10;

            ti.Distance =
                    uByteToInt(info[c]) * 16777216 +
                    uByteToInt(info[c + 1]) * 65536 +
                    uByteToInt(info[c + 2]) * 256 +
                    uByteToInt(info[c + 3]);
            c++;
            c++;
            c++;
            c++;

            ti.Calories = uByteToInt(info[c]) * 256 + uByteToInt(info[c + 1]);
            c++;
            c++;

            ti.TopSpeed = uByteToInt(info[c]) * 256 + uByteToInt(info[c + 1]);
            c++;
            c++;

            ti.TopHeartBeat = uByteToInt(info[c]);
            c++;

            ti.AvgHeartBeat = uByteToInt(info[c]);
            c++;

            ti.AscHeight = uByteToInt(info[c]) * 256 + uByteToInt(info[c + 1]);
            c++;
            c++;

            ti.DescHeight = uByteToInt(info[c]) * 256 + uByteToInt(info[c + 1]);
            c++;
            c++;

            ti.Points = uByteToInt(info[c]) * 256 + uByteToInt(info[c + 1]);
            c++;
            c++;

            ti.ID = uByteToInt(info[c]) * 256 + uByteToInt(info[c + 1]);

            TrackList.put(ti.ID, ti);
            trcount++;
        }
    }

    private static void parseInfo(byte[] info) {
        int c = 0;

        // Device name
        c = 3;
        DeviceInfo.Device = "";
        while (info[c] != 0) {
            DeviceInfo.Device += (char) info[c];
            c++;
        }

        // User Name
        c = 45;
        DeviceInfo.Name = "";
        while (info[c] != 0) {
            DeviceInfo.Name += (char) info[c];
            c++;
        }

        // User sex
        if (info[56] == 0) {
            DeviceInfo.Sex = "Male";
        } else {
            DeviceInfo.Sex = "Female";
        }

        // User age
        DeviceInfo.Age = uByteToInt(info[57]);

        // User weight & height
        DeviceInfo.Weight_pounds = uByteToInt(info[59]);
        DeviceInfo.Weight_kg = uByteToInt(info[61]);
        DeviceInfo.Height_inch = uByteToInt(info[63]);
        DeviceInfo.Height_cm = Integer.valueOf(uByteToInt(info[65]));

        // Tracks count
        DeviceInfo.TracksCount = uByteToInt(info[67]);

        // Birth date
        DeviceInfo.Year = uByteToInt(info[69]) * 256 + uByteToInt(info[70]);
        DeviceInfo.Month = uByteToInt(info[71]);
        DeviceInfo.Day = uByteToInt(info[72]);
    }

    private static int uByteToInt(byte b) {
        return (int) b & 0xFF;
    }
}
